package com.shiro1.realm;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.sql.DataSource;

import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.JdbcUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyJdbcRealm extends AuthorizingRealm {
	private static final Logger log = LoggerFactory.getLogger(MyJdbcRealm.class);
	// 对下面的sql语句，修改了一下表名。其他的字段名的修改可根据自己的情况修改。
	// 根我们自己的表名情况定义自己的认证查询语句
	protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from t_users where username = ?";
	// 根据我们自己的表名字段及相关情况查询自己用户角色
	protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from t_user_roles where username = ?";
	// 根据角色查询权限
	protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from t_roles_permissions where t_role_name = ?";

	protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;

	protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;

	protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;

	protected boolean permissionsLookupEnabled = false;



	protected DataSource dataSource;// 要查询数据库，我们需要数据源
	

	protected SaltStyle saltStyle = SaltStyle.NO_SALT;

	public enum SaltStyle {
		NO_SALT, CRYPT, COLUMN, EXTERNAL
	};

	
	public boolean isPermissionsLookupEnabled() {
		return permissionsLookupEnabled;
	}

	public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) {
		this.permissionsLookupEnabled = permissionsLookupEnabled;
	}
	
	public DataSource getDataSource() {
		return dataSource;
	}

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	/**
	 * shiro想要得到授权信息.我们从自己的数据库查询后，将验证信息返回即可 具体可参考shiro默认的JdbcRealm代码。参考代码时。
	 * 主要看其中的doGetAuthorizationInfo.的实现。其他的与自己的表名和结构相关。
	 * 我们只需根据自己的数据库表来写相关sql语句即可。
	 * 其实，我们还有一个偷懒的方法。设置默认JdbcRealm的三个与数据查询相关的属性。有可能满足我们的要求 protected String
	 * authenticationQuery = DEFAULT_AUTHENTICATION_QUERY; protected String
	 * userRolesQuery = DEFAULT_USER_ROLES_QUERY; protected String
	 * permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		 //null usernames are invalid
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }

        String username = (String) getAvailablePrincipal(principals);

        Connection conn = null;
        Set<String> roleNames = null;
        Set<String> permissions = null;
        try {
            conn = dataSource.getConnection();

            // Retrieve roles and permissions from database
            roleNames = getRoleNamesForUser(conn, username);
            if (permissionsLookupEnabled) {
                permissions = getPermissions(conn, username, roleNames);
            }

        } catch (SQLException e) {
            final String message = "There was a SQL error while authorizing user [" + username + "]";
            if (log.isErrorEnabled()) {
                log.error(message, e);
            }

            // Rethrow any SQL errors as an authorization exception
            throw new AuthorizationException(message, e);
        } finally {
            JdbcUtils.closeConnection(conn);
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
        info.setStringPermissions(permissions);
        return info;
	}

	protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
        PreparedStatement ps = null;
        Set<String> permissions = new LinkedHashSet<String>();
        try {
            ps = conn.prepareStatement(permissionsQuery);
            for (String roleName : roleNames) {

                ps.setString(1, roleName);

                ResultSet rs = null;

                try {
                    // Execute query
                    rs = ps.executeQuery();

                    // Loop over results and add each returned role to a set
                    while (rs.next()) {

                        String permissionString = rs.getString(1);

                        // Add the permission to the set of permissions
                        permissions.add(permissionString);
                    }
                } finally {
                    JdbcUtils.closeResultSet(rs);
                }

            }
        } finally {
            JdbcUtils.closeStatement(ps);
        }

        return permissions;
    }
	
	
	protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        Set<String> roleNames = new LinkedHashSet<String>();
        try {
            ps = conn.prepareStatement(userRolesQuery);
            ps.setString(1, username);

            // Execute query
            rs = ps.executeQuery();

            // Loop over results and add each returned role to a set
            while (rs.next()) {

                String roleName = rs.getString(1);

                // Add the role to the list of names if it isn't null
                if (roleName != null) {
                    roleNames.add(roleName);
                } else {
                    if (log.isWarnEnabled()) {
                        log.warn("Null role name found while retrieving role names for user [" + username + "]");
                    }
                }
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }
        return roleNames;
    }
	
	/**
	 * shiro想要得到验证信息。我们从自己的数据库查询后，将授权信息返回即可. 具体可参考shiro默认的JdbcRealm 实现原理同上
	 * 另外，我们可以使用服务层或dao来实获取密码等相关
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		String username = upToken.getUsername();

		// Null username is invalid
		if (username == null) {
			throw new AccountException("Null usernames are not allowed by this realm.");
		}

		Connection conn = null;
		SimpleAuthenticationInfo info = null;
		try {
			conn = dataSource.getConnection();

			String password = null;
			String salt = null;
			switch (saltStyle) {
			case NO_SALT:
				password = getPasswordForUser(conn, username)[0];
				break;
			case CRYPT:
				// TODO: separate password and hash from getPasswordForUser[0]
				throw new ConfigurationException("Not implemented yet");
				// break;
			case COLUMN:
				String[] queryResults = getPasswordForUser(conn, username);
				password = queryResults[0];
				salt = queryResults[1];
				break;
			case EXTERNAL:
				password = getPasswordForUser(conn, username)[0];
				salt = getSaltForUser(username);
			}

			if (password == null) {
				throw new UnknownAccountException("No account found for user [" + username + "]");
			}

			info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());

			if (salt != null) {
				info.setCredentialsSalt(ByteSource.Util.bytes(salt));
			}

		} catch (SQLException e) {
			final String message = "There was a SQL error while authenticating user [" + username + "]";
			if (log.isErrorEnabled()) {
				log.error(message, e);
			}

			// Rethrow any SQL errors as an authentication exception
			throw new AuthenticationException(message, e);
		} finally {
			JdbcUtils.closeConnection(conn);
		}

		return info;
	}

	protected String getSaltForUser(String username) {
		return username;
	}

	private String[] getPasswordForUser(Connection conn, String username) throws SQLException {

		String[] result;
		boolean returningSeparatedSalt = false;
		switch (saltStyle) {
		case NO_SALT:
		case CRYPT:
		case EXTERNAL:
			result = new String[1];
			break;
		default:
			result = new String[2];
			returningSeparatedSalt = true;
		}

		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			ps = conn.prepareStatement(authenticationQuery);
			ps.setString(1, username);

			// Execute query
			rs = ps.executeQuery();

			// Loop over results - although we are only expecting one result,
			// since usernames should be unique
			boolean foundResult = false;
			while (rs.next()) {

				// Check to ensure only one row is processed
				if (foundResult) {
					throw new AuthenticationException(
							"More than one user row found for user [" + username + "]. Usernames must be unique.");
				}

				result[0] = rs.getString(1);
				if (returningSeparatedSalt) {
					result[1] = rs.getString(2);
				}

				foundResult = true;
			}
		} finally {
			JdbcUtils.closeResultSet(rs);
			JdbcUtils.closeStatement(ps);
		}

		return result;
	}

}
